package com.ccp.dev.core.basic.mybatis;

import com.ccp.dev.core.basic.page.PageBean;
import com.ccp.dev.core.basic.web.query.QueryFilter;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 为MyBatis提供基于方言(Dialect)的分页查询的插件
 * <p>
 * 将拦截StatementHandler.prepare()方法实现分页方言的插入.
 *
 * @author badqiu
 * @author miemiedev
 */
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class OffsetLimitInterceptor implements Interceptor {
    private static Logger logger = LoggerFactory.getLogger(OffsetLimitInterceptor.class);

    @Value("${mybatis.configuration.database-id}")
    private String dbType;

    /**
     * 拦截后的实际处理
     *
     * @param invocation 通过反射机制获取的@Signature中拦截的类，方法，参数的实例
     * @return 拦截方法的返回值 这里的返回值则为StatementHandler.prepare方法执行后的返回值.
     * @throws Throwable
     */
    @Override
    public Object intercept(final Invocation invocation) throws Throwable {

        //获取数据库连接
        Connection connection = (Connection) invocation.getArgs()[0];
        //获取拦截的StatementHandler接口示例对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //获取mybatis中存储sql语句实例的对象
        BoundSql boundSql = statementHandler.getBoundSql();
        //获取mybatis中存储mapper中参数 实例的对象
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();

        Object parameter = statementHandler.getParameterHandler().getParameterObject();
        if (parameter == null || !(parameter instanceof QueryFilter)) {
            //没有参数或者参数类型不是QueryFilter的 不需要做分页处理
            return invocation.proceed();
        }
        QueryFilter queryFilter = (QueryFilter) parameter;
        PageBean pageBean = queryFilter.getPageBean();
        //没有分页设置时 也不做分页处理
        if (pageBean == null) {
            return invocation.proceed();
        }
        int currentPage = pageBean.getCurrentPage();
        //每页数据条数
        int pageSize = pageBean.getPageSize();
        //页码
        final StringBuffer bufferSql = new StringBuffer(boundSql.getSql().trim());
        if (bufferSql.lastIndexOf(";") == bufferSql.length() - 1) {
            bufferSql.deleteCharAt(bufferSql.length() - 1);
        }
        //sql语句。
        String sql = getSql(bufferSql.toString().trim());
        if (pageBean.isShowTotal()) {
            //统计数据总记录条数
            setPageTotal(connection, boundSql, pageBean, parameterHandler);
        }
        Dialect dialect =PageDialectEnum.getDialectByDBType(dbType);
        //支持分页
        if (dialect.supportsLimit()) {
            //是否支持分页
            if (dialect.supportsLimitOffset() && (currentPage != RowBounds.NO_ROW_OFFSET || pageSize != RowBounds.NO_ROW_LIMIT)) {
                int start = (currentPage - 1) * pageSize;
                int end = currentPage * pageSize;
                if(PageDialectEnum.MYSQL.getType().equalsIgnoreCase(dbType) || PageDialectEnum.POSTGRESQL.getType().equalsIgnoreCase(dbType)){
                    end = pageSize;
                }
                sql = dialect.getLimitString(sql, start, end);
            } else {
                sql = dialect.getLimitString(sql, 0, RowBounds.NO_ROW_LIMIT);
            }
            //利用mybatis原生的方法 强行将sql语句写入boundSql中 private final String sql 的字段
            MetaObject metaObject =
                    MetaObject.forObject(boundSql, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                            SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
            metaObject.setValue("sql", sql);
        }
        return invocation.proceed();
    }
    private static String getSql(String sql) {
        return sql.trim().replaceAll("(?si)\\s+", " ");
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
    /**
     * 功能描述: 获取查询总数的sql
     *
     * @param: sql 原始sql
     * @return: java.lang.String
     * @auther: dfz
     * @date: 2019/4/28 9:07
     */
    private String getCountSql(String sql) {
        //获取查询总条数的sql
        StringBuffer sb = new StringBuffer("select count(1) from (");
        /*sql = sql.toLowerCase();
        if (sql.lastIndexOf("order") > sql.lastIndexOf(")")) {
            sb.append(sql.substring(sql.indexOf("from") + 4, sql.lastIndexOf("order")));
        } else {
            sb.append(sql.substring(sql.indexOf("from") + 4));
        }*/
        sb.append(sql);
        sb.append(")  tab");
        return sb.toString();
    }

    /**
     * 分页的总数设置
     *
     * @param connection       数据库连接
     * @param boundSql         绑定的sql
     * @param pageBean         分页对象
     * @param parameterHandler 参数
     */
    private void setPageTotal(Connection connection, BoundSql boundSql, PageBean pageBean, ParameterHandler parameterHandler) {
        String countSql = getCountSql(boundSql.getSql());
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            preparedStatement = connection.prepareStatement(countSql);
            //利用mybatis原生方法 对sql语句设置参数
            parameterHandler.setParameters(preparedStatement);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                pageBean.setTotalCount(resultSet.getInt(1));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            closeResultAndPrepare(resultSet,preparedStatement);
        }
    }

    /**
     * 释放资源
     * @param resultSet 结果集
     * @param preparedStatement 查询条件
     */
    public void closeResultAndPrepare(ResultSet resultSet,PreparedStatement preparedStatement){
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (preparedStatement != null) {
                preparedStatement.close();
            }
        } catch (SQLException e) {
            logger.debug("无法释放资源: {} ", e);
            e.printStackTrace();
        }
    }



}
